查看原文
其他

【周末AI课堂】Boosting集成(代码篇)| 机器学习你会遇到的“坑”

唐僧不用海飞丝 读芯术 2019-12-26


AI课堂开讲,就差你了!


很多人说,看了再多的文章,可是没有人手把手地教授,还是很难真正地入门AI。为了将AI知识体系以最简单的方式呈现给你,从这个星期开始,芯君邀请AI专业人士开设“周末学习课堂”——每周就AI学习中的一个重点问题进行深度分析,课程会分为理论篇和代码篇,理论与实操,一个都不能少!


来,退出让你废寝忘食的游戏页面,取消只有胡吃海塞的周末聚会吧。未来你与同龄人的差异,也许就从每周末的这堂AI课开启了!


读芯术读者交流群,请加小编微信号:zhizhizhuji。等你。

全文共2154字,预计学习时长5分钟




在《bagging集成和stacking集成》中,我们曾经发现了无论是随机森林还是Extremely Randomized Trees,随着基学习器的增加,泛化误差都会趋于稳定。


图为随机森林的结果


图为Extremely Randomized Trees的结果


同时,我们也简单证明了boosting集成就不会遇到这样的问题,因为每一个基学习器都要针对上一轮学习器的结果进行优化,Adaboost的分类版本会更关注分类错误的样本,Gradient Boosting的回归版本会去继续优化Loss的负梯度。首先我们使用Adaboost适应IRIS数据,以决策树作为基学习器,我们在上一节的代码篇中得到了决策树的叶节点的最小样本为3,所以我们基学习器的minsamplesleaf也设置为3,进一步观察基学习器数量对泛化误差的影响:

import numpy as np

import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets
fromsklearn.model_selection import cross_validate
from sklearn.ensemble import AdaBoostClassifier as ABC
from sklearn.tree import DecisionTreeClassifieras DTC

iris = datasets.load_iris()
X = iris.data
y = iris.target

dtc=DTC(min_samples_leaf=
3)
    
test_mse=[]
train_mse=[]
numbers=range(
1,50)
for d in numbers:
  clf=ABC(base_estimator=dtc,n_estimators=d) 
  clf_dict=cross_validate(clf,X,y,cv=
10,scoring='accuracy',algorithm='SAMME')
  test_mse.append(clf_dict[
'test_score'].mean())
  train_mse.append(clf_dict[
'train_score'].mean())
  
sns.set(style=
'darkgrid')
plt.plot(numbers,train_mse,
'b-.',label='Train Accuracy')
plt.plot(numbers,test_mse,
'r-.',label='Test Accuracy')
plt.xlabel(
' n estimators')
plt.ylabel(
'Accuracy')
plt.title(
'DecisionTree forAdaboost')
plt.legend()
plt.show()


如图,我们可以看到在一开始的时候测试准确率就非常高,添加了基学习器之后,测试准确率并没有增加。


回顾Adaboost的整个过程,一开始,我们使用学习器应用在全部数据上,训练完成后,我们利用错误率更新学习器的权重,并利用对应的损失更新样本的权重,如果一开始的时候,我们就利用一个强学习器,那么错误率本身就很低,继续添加学习器,测试准确率就不会变化。所以为了Adaboost显示出威力,我们在一开始的更合适的做法是,构建弱学习器。


我们的方法是限制决策树的最大深度为1,因为当决策树的深度为1的时候,只对属性进行了一次划分,一般而言效果都是差的。

....

dtc=DTC(max_depth=1)
....


从图中可以看出,随着基学习器数量的增加,测试准确率先急速上升,然后稳定。


我们还可以观察基学习器数量的增加,决策边界的变化:

import numpy as np

import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.ensemble import AdaBoostClassifier as ABC
from sklearn.tree import DecisionTreeClassifieras DTC
import seaborn as sns

iris = datasets.load_iris()
X = iris.data[:, :
2]
y = iris.target

def make_meshgrid(x, y, h=.02):
    x_min, x_max = x.min() -
1, x.max() +1
    y_min, y_max = y.min() -
1, y.max() +1
    xx, yy =np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min,y_max, h))
    
return(xx, yy)
  
xx,yy=make_meshgrid(X[:,
0],X[:,1])

dtc=DTC(max_depth=
1)


numbers=[
1,5,10,50]
sns.set(style=
'darkgrid')  
for k,j in enumerate(numbers):
  clf =ABC(base_estimator=dtc,n_estimators=j)
  clf.fit(X,y)
  Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
  Z = Z.reshape(xx.shape)
  plt.subplot(len(numbers)/
2,len(numbers)/2,k+1)
  plt.contourf(xx,yy,Z,cmap=plt.cm.RdBu,alpha=
0.6)
  
for i,v,l in [[0,'r','setosa'],[1,'g','vericolor'],[2,'b','virginica']]:
    plt.scatter(X[y==i][:,
0],X[y==i][:,1],c=v,label=l,edgecolor='k')
  plt.title(
'$n=$%s'%j)
  plt.legend()

plt.show()


从图中可以看出,当基学习器为1的时候,决策边界非常简单,因为只进行了一次划分,与理论相符。随着基学习器数量的增加,决策边界变得越来越复杂,直到基学习器数量为50的时候,甚至为左边的蓝点单独创立了规则。


此时,我们出现了一个矛盾的结果,根据决策边界,基学习器数量的增加会导致一定程度的过拟合,但根据测试集的表现,又未出现过拟合。那么,一个严肃的问题摆在我们面前,对于Adaboost,基学习器数量的增加是否会导致过拟合呢?甚至人们在有些情况下还会发现,训练误差为零的情况下,测试性能仍然能进一步提升。


一般认为,在bagging中,泛化误差的减小主要来源于方差的降低;在boosting中,泛化误差的减小主要来源于偏差的降低。前者更容易出现欠拟合,后者则更容易出现过拟合。根据我们原本的理论,基学习器数量的增加会增大模型的复杂度,势必会在某个节点出现过拟合,泛化误差下降,但从另一方面来说,Adaboost的对模型的加权平均,不能纠正错误的模型权重系数也会很小,对结果似乎也不会产生大的影响。


这个问题目前并没有得到彻底的解决,目前比较有说服力的是间隔理论(Margin theroy),所谓的Margin可以简单的理解为决策边界离样本的远近,因为虽然决策边界内包含了很多点,但离决策边界的远近程度却不一样,离得远的样本点不容易随着决策边界的变化而变化,而处在决策边界边缘的样本点却对变化非常敏感,所以即使训练误差为零,但泛化误差还有上升的空间,因为此时的优化过程是在扩大决策边界与样本的最小间隔,而利用传统的分析方法是无法看到这样的过程。从数学上来说,就是改变了泛化误差的上界。


按照这个理论,Adaboost训练集上的稳定,实际上却是在不断对决策边界进行调整,而Adaboost每次仅更新一个参数,也会让学习过程变得非常缓慢。


关于Gradient Boosting的实现较为简单,当我们使用决策树作为基学习器时,自然可以通过剪枝来限制每棵树的过拟合程度,我们前面的做法已经证明了在boosting中对单棵树做限制很可能是无效的,所以必须清楚两种正则化的手段:


  • 将普通的加性模型:

 


再次添加learning rate:



这样一种参数化的办法实际上是把调节了每棵树的影响,使得后面具有更大的学习空间。


  • subsampling:


借用了bagging中的Bootstrap做法,与bootstrap不同的是,它是不放回的采样。需要注意的是,每次迭代的时候,我们都会进行全新的不放回采样。这将有助于减小方差,降低过拟合的风险。


这两种正则化方式也被叫做shrinkage。我们试着对数据做梯度提升,并设置learning rate和subsampling,来观察对泛化性能的影响:

import numpy as np

import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import ensemble
from sklearn import datasets
fromsklearn.model_selection import train_test_split


X, y =datasets.make_hastie_10_2(n_samples=
12000, random_state=1)
X = X.astype(np.float32)

labels, y = np.unique(y,return_inverse=
True)

X_train, X_test,y_train, y_test = train_test_split(
    X, y, test_size=
0.8,shuffle=False)

original_params = {
'n_estimators'1000'max_leaf_nodes'4'max_depth'None,'random_state'2,
                   
'min_samples_split'5}

sns.set(style=
'darkgrid')

for label, color, setting in [('No shrinkage''orange',
                               {
'learning_rate'1.0'subsample'1.0}),
                              (
'learning_rate=0.1''turquoise',
                               {
'learning_rate'0.1'subsample'1.0}),
                              (
'subsample=0.5''blue',
                               {
'learning_rate'1.0'subsample'0.5}),
                              (
'learning_rate=0.1,subsample=0.5''gray',
                               {
'learning_rate'0.1'subsample'0.5})]:
    params =dict(original_params)
    params.update(setting)

    clf = ensemble.GradientBoostingClassifier(**params)
    clf.fit(X_train, y_train)

    test_deviance = np.zeros((params[
'n_estimators'],), dtype=np.float64)

    
for i, y_pred in enumerate(clf.staged_decision_function(X_test)):
        test_deviance[i] = clf.loss_(y_test,y_pred)

    plt.plot((np.arange(test_deviance.shape[
0]) +1)[::5], test_deviance[::5],
            '-', color=color, label=label)

plt.legend()
plt.xlabel(
'Boosting Iterations')
plt.ylabel(
'Test Set Deviance')

plt.show()


如图,随着基学习器的增加,添加了subsampling和learning rate的曲线测试误差最终收敛到了一个很低的区间,没有添加任何正则化手段的提升树,在基学习器大约为200的时候就产生了微弱的过拟合。


读芯君开扒


课堂TIPS


• Adaboost为什么不会产生过拟合,仍然是一个具有挑战性的问题。而最近的研究也在表明,Adaboost不仅是降低了偏差,也降低了方差,因为在每一轮学习的过程中,每次进入学习器的样本的分布发生了变化,类似于bagging的采样过程,也是一种数据扰动。


• 在Adaboost中,我们也可以添加learning rate,只是在sklearn的Adaboost类中,learning rate默认为1,就相当于没有添加,我们在实际使用过程中可以自行更改。


• 我们在上一章节所讲授的Adaboost用于分类,gradient boosting用于回归,实际上,两者经过推广后都可以用于分类和回归任务。


留言 点赞 发个朋友圈

我们一起探讨AI落地的最后一公里


作者:唐僧不用海飞丝


如需转载,请后台留言,遵守转载规范


推荐文章阅读


【周末AI课堂 | 第二十一讲】Boosting集成(理论篇)

【周末AI课堂 | 第二十讲】bagging集成和stacking集成(代码篇)

【周末AI课堂 | 第十九讲】bagging集成和stacking集成(理论篇)

【周末AI课堂 | 第十八讲】非参模型进阶(代码篇)

【周末AI课堂 | 第十七讲】非参模型进阶(理论篇)

【周末AI课堂 | 第十六讲】非参模型初步(代码篇)

【周末AI课堂 | 第十五讲】非参模型初步(理论篇)

【周末AI课堂 | 第十四讲】基于贝叶斯推断的回归模型(代码篇)

【周末AI课堂 | 第十三讲】基于贝叶斯推断的回归模型(理论篇)

【周末AI课堂 | 第十二讲】基于贝叶斯推断的分类模型(代码篇)

【周末AI课堂 | 第十一讲】基于贝叶斯推断的分类模型(理论篇)

【周末AI课堂 | 第十讲】核技巧(代码篇)

【周末AI课堂 | 第九讲】核技巧(理论篇)

【周末AI课堂 | 第八讲】非线性降维方法(代码篇)

【周末AI课堂 | 第七讲】非线性降维方法(理论篇)

【周末AI课堂 | 第六讲】线性降维方法(代码篇)

【周末AI课堂 | 第五讲】线性降维方法(理论篇)

【周末AI课堂 | 第四讲】如何进行特征选择(代码篇)

【周末AI课堂 | 第三讲】如何进行特征选择(理论篇)

【周末AI课堂 | 第二讲】过拟合问题(代码篇)

【周末AI课堂 | 第一讲】过拟合问题(理论篇)


长按识别二维码可添加关注

读芯君爱你


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存